﻿using System;
using System.Collections.Generic;
using System.Text;

using Roslyn.Compilers;
using Roslyn.Compilers.CSharp;

using RexToy;

namespace ScriptXCompiler
{
    public partial class Translator
    {
        private bool IsAssignExpression(BinaryExpressionSyntax syntax)
        {
            switch (syntax.Kind)
            {
                case SyntaxKind.AssignExpression:
                case SyntaxKind.AddAssignExpression:
                case SyntaxKind.AndAssignExpression:
                case SyntaxKind.DivideAssignExpression:
                case SyntaxKind.ExclusiveOrAssignExpression:
                case SyntaxKind.LeftShiftAssignExpression:
                case SyntaxKind.ModuloAssignExpression:
                case SyntaxKind.MultiplyAssignExpression:
                case SyntaxKind.OrAssignExpression:
                case SyntaxKind.RightShiftAssignExpression:
                case SyntaxKind.SubtractAssignExpression:
                    return true;

                default:
                    return false;
            }
        }

        private string DecomposeOperator(SyntaxKind kind)
        {
            switch (kind)
            {
                case SyntaxKind.AddAssignExpression:
                    return "+";

                case SyntaxKind.AndAssignExpression:
                    return "&";

                case SyntaxKind.DivideAssignExpression:
                    return "/";

                case SyntaxKind.ExclusiveOrAssignExpression:
                    return "^";

                case SyntaxKind.LeftShiftAssignExpression:
                    return "<<";

                case SyntaxKind.ModuloAssignExpression:
                    return "%";

                case SyntaxKind.MultiplyAssignExpression:
                    return "*";

                case SyntaxKind.OrAssignExpression:
                    return "|";

                case SyntaxKind.RightShiftAssignExpression:
                    return ">>";

                case SyntaxKind.SubtractAssignExpression:
                    return "-";

                default:
                    CompileIssue issue = new CompileIssue(IssueType.Error, IssueId.UnknownCompositeOperator, kind);
                    this._aggregator.AppendIssue(issue);
                    return string.Empty;
            }
        }

        private bool IsPropertyAccess(ExpressionSyntax syntax)
        {
            MemberAccessExpressionSyntax maes = syntax as MemberAccessExpressionSyntax;
            if (maes == null)
                return false;
            var info = _semanticModel.GetSemanticInfo(maes);
            if (info.Symbol.Kind == SymbolKind.Property)
                return true;
            else
                return false;
        }

        private bool IsPartOfAssignment(SyntaxNode node)
        {
            var bes = node.Parent as BinaryExpressionSyntax;
            if (bes != null && IsAssignExpression(bes))
            {
                return true;
            }
            var evcs = node.Parent as EqualsValueClauseSyntax;
            if (evcs != null)
            {
                return true;
            }
            return false;
        }

        private string MakeInterfacesList(ReadOnlyArray<NamedTypeSymbol> interfaces)
        {
            StringBuilder str = new StringBuilder("[");
            foreach (var info in interfaces)
            {
                if (!IsScriptSymbol(info))
                {
                    var issue = new CompileIssue(IssueType.Error, IssueId.UseNonScript, info);
                    _aggregator.AppendIssue(issue);
                }
                str.AppendFormat("{0}, ", this.GetTypeSymbolName(info));
            }
            if (interfaces.Count > 0)
                str.RemoveEnd(", ");
            str.Append("]");
            return str.ToString();
        }

        private string MakeParametersList(ParameterListSyntax syntax)
        {
            var args = syntax.Parameters;
            StringBuilder str = new StringBuilder();
            if (args.Count > 0)
            {
                var fstArg = args[0];
                foreach (var m in fstArg.Modifiers)
                {
                    if (m.Kind == SyntaxKind.ThisKeyword)
                    {
                        var issue = new CompileIssue(IssueType.Error, IssueId.ExtensionMethodNotSupport);
                        _aggregator.AppendIssue(issue);
                    }
                }

                foreach (var arg in args)
                {
                    var info = _semanticModel.GetDeclaredSymbol(arg);
                    if (!IsScriptSymbol(info.Type))
                    {
                        var issue = new CompileIssue(IssueType.Error, IssueId.UseNonScript, info.Type);
                        _aggregator.AppendIssue(issue);
                    }

                    if (arg.DefaultOpt != null)
                    {
                        var issue = new CompileIssue(IssueType.Error, IssueId.DefaultParamNotSupport) { SyntaxItem = syntax };
                        _aggregator.AppendIssue(issue);
                    }
                    str.Append(arg.Identifier.ValueText).Append(", ");
                }
                str.RemoveEnd(", ");
            }
            return str.ToString();
        }

        private void MakeLocalVariableList(VariableDeclarationSyntax syntax)
        {
            int count = 0;
            foreach (var v in syntax.Variables)
            {
                if (count == 0)
                {
                    _output.Write(v.Identifier.ValueText);
                }
                else
                {
                    _output.Write(", ");
                    _output.Write(v.Identifier.ValueText);
                }

                if (v.InitializerOpt != null)
                {
                    _output.Write(" = ");
                    this.Visit(v.InitializerOpt);
                }
                count++;
            }
        }

        private void MakeArgumentsList(SeparatedSyntaxList<ArgumentSyntax> syntaxList)
        {
            int count = 0;
            foreach (var arg in syntaxList)
            {
                if (arg.NameColonOpt != null)
                {
                    var issue = new CompileIssue(IssueType.Error, IssueId.NamedArgumentNotSupport) { SyntaxItem = arg };
                    _aggregator.AppendIssue(issue);
                }

                this.VisitExpression(arg.Expression);
                count++;
                if (count != syntaxList.Count)
                    _output.Write(", ");
            }
        }

        private void MakeExpressionList(SeparatedSyntaxList<ExpressionSyntax> list)
        {
            int count = 0;
            foreach (var expr in list)
            {
                if (count != 0)
                    _output.Write(", ");

                this.VisitExpression(expr);
                count++;
            }
        }

        private string GetDefaultValueOfType(TypeSyntax type)
        {
            var info = _semanticModel.GetSemanticInfo(type);
            var cType = info.ConvertedType;

            if (this.IsEnumerable(info))
            {
                return "[]";
            }

            var attrs = cType.GetAttributes();
            foreach (var attr in attrs)
            {
                if (attr.AttributeClass.Name == "ScriptDefaultValueAttribute" && attr.AttributeClass.ContainingNamespace.ToString() == "System")
                {
                    var val = attr.NamedArguments[0].Value;
                    return (string)val.Value;
                }
            }

            return ("null");
        }

        private string TranslateUnaryOperator(SyntaxToken token)
        {
            switch (token.Kind)
            {
                case SyntaxKind.PlusPlusToken:
                    return " + 1";

                case SyntaxKind.MinusMinusToken:
                    return " - 1";

                default:
                    var issue = new CompileIssue(IssueType.Error, IssueId.UnknownUnaryOperator) { SyntaxItem = token };
                    _aggregator.AppendIssue(issue);
                    return string.Empty;
            }
        }

        private string GetTypeSymbolName(Symbol symbol)
        {
            var name = string.Empty;
            var attrs = symbol.GetAttributes();
            foreach (var attr in attrs)
            {
                if (attr.AttributeClass.Name == "ImportClassAttribute" && attr.AttributeClass.ContainingNamespace.ToString() == "System")
                {
                    var ns = attr.PositionalArguments[0].Value;
                    var cs = attr.PositionalArguments[1].Value;

                    var fqn = string.Format("{0}.{1}", ns, cs);
                    name = fqn.TrimStart('.').TrimEnd('.');

                    return name;
                }
            }

            return symbol.ContainingNamespace + "." + symbol.Name;
        }

        private bool IsNativeMethod(Symbol symbol)
        {
            var attrs = symbol.GetAttributes();
            foreach (var attr in attrs)
            {
                if (attr.AttributeClass.ContainingNamespace.ToString() == "System" && attr.AttributeClass.Name == "JsNativeAttribute")
                {
                    return true;
                }
            }

            return false;
        }

        private bool IsEnumerable(SemanticInfo info)
        {
            //Extend:More type, IEnumerable<T>...
            if (info.Type.TypeKind == TypeKind.ArrayType || info.Type.SpecialType == SpecialType.System_Array || info.Type.SpecialType == SpecialType.System_Collections_IEnumerable)
                return true;
            foreach (var inf in info.Type.AllInterfaces)
            {
                if (inf.Name == "IEnumerable" && inf.ContainingNamespace.ToString() == "System.Collections")
                    return true;
            }
            return false;
        }

        private bool IsScriptSymbol(Symbol symbol)
        {
            var attrs = symbol.GetAttributes();
            foreach (var attr in attrs)
            {
                if (attr.AttributeClass.Name == "NonScriptAttribute" && attr.AttributeClass.ContainingNamespace.ToString() == "System")
                {
                    return false;
                }
            }
            return true;
        }

        private bool IsEvalCandidate(Symbol symbol)
        {
            var attrs = symbol.GetAttributes();
            foreach (var attr in attrs)
            {
                if (attr.AttributeClass.Name == "EvalAtCompileAttribute" && attr.AttributeClass.ContainingNamespace.ToString() == "System")
                {
                    return true;
                }
            }
            return false;
        }

        private string GetMemberSymbolName(Symbol symbol)
        {
            var attrs = symbol.GetAttributes();
            foreach (var attr in attrs)
            {
                if (attr.AttributeClass.Name == "ImportMemberAttribute" && attr.AttributeClass.ContainingNamespace.ToString() == "System")
                {
                    var val = attr.PositionalArguments[0].Value;
                    return (string)val;
                }
            }

            return NamingConvention.LowerCase1stChar(symbol.Name);
        }

        private bool IsGlobalHolder(ExpressionSyntax syntax)
        {
            var info = _semanticModel.GetSemanticInfo(syntax);
            if (info.Symbol != null && info.Symbol.Kind == SymbolKind.NamedType)
            {
                var name = this.GetTypeSymbolName(info.Symbol);
                return string.IsNullOrEmpty(name);
            }

            return false;
        }

        public string GetDeclareOperator()
        {
            return _isStaticMember ? " = " : ": ";
        }

        private bool IsRightMostMemberAccess(MemberAccessExpressionSyntax node)
        {
            var parent = node.Parent;

            while (parent.Kind == SyntaxKind.ElementAccessExpression)
                parent = parent.Parent;
            if (parent.Kind == SyntaxKind.MemberAccessExpression)
                return false;
            else
                return true;
        }

        private bool IsNoCompile(MemberDeclarationSyntax syntax)
        {
            var info = _semanticModel.GetDeclaredSymbol(syntax);
            foreach (var attr in info.GetAttributes())
            {
                if (attr.AttributeClass.Name == "NoCompileAttribute" && attr.AttributeClass.ContainingNamespace.ToString() == "System")
                    return true;
            }
            return false;
        }

        private void VisitExpression(ExpressionSyntax syntax)
        {
            var info = _semanticModel.GetSemanticInfo(syntax);

            if (info.IsCompileTimeConstant && info.Symbol != null && IsEvalCandidate(info.Symbol))
            {
                if (info.IsCompileTimeConstant)
                {
                    if (info.ConstantValue == null)
                        _output.Write("null");
                    else
                    {
                        if (info.ConstantValue is string)
                        {
                            _output.Write("\"" + info.ConstantValue.ToString() + "\"");
                        }
                        else
                        {
                            _output.Write(info.ConstantValue.ToString());
                        }
                    }
                }
                else
                {
                    var issue = new CompileIssue(IssueType.Error, IssueId.NotConstant, syntax) { SyntaxItem = syntax };
                    _aggregator.AppendIssue(issue);
                }
            }
            else
            {
                Visit(syntax);
            }
        }

        private void AppendCompensateSemicolon(StatementSyntax syntax)
        {
            if (!(syntax is BlockSyntax))
            {
                if (syntax.Kind == SyntaxKind.ExpressionStatement || syntax.Kind == SyntaxKind.ThrowStatement)
                    _output.WriteLine(";");
            }
        }

        private bool GetEnumValue(Symbol symbol, out string val)
        {
            foreach (var attr in symbol.GetAttributes())
            {
                if (attr.AttributeClass.Name == "EnumValueAttribute" && attr.AttributeClass.ContainingNamespace.ToString() == "System")
                {
                    if (attr.PositionalArguments[0].Value == null)
                        val = null;
                    else
                        val = string.Format("\"{0}\"", attr.PositionalArguments[0].Value);
                    return true;
                }
            }

            val = null;
            return false;
        }

        //Extend:this method exist because current version API can not support object create initailize syntax, should remove in future!
        private string BindSemanticModel(ObjectCreationExpressionSyntax syntax, SyntaxToken identifier)
        {
            var info = _semanticModel.GetSemanticInfo(syntax.Type);

            return _BindTypeMember(info.Type, identifier.ValueText);
        }

        private string _BindTypeMember(TypeSymbol symbol, string memberName)
        {
            var members = symbol.GetMembers(memberName);
            switch (members.Count)
            {
                case 1:
                    var member = members[0];
                    return this.GetMemberSymbolName(member);

                case 0:
                    var t = symbol.BaseType;
                    if (t != null && !(t is ErrorTypeSymbol))
                    {
                        return _BindTypeMember(t, memberName);
                    }
                    else
                    {
                        CompileIssue issue = new CompileIssue(IssueType.Error, IssueId.SemanticBind) { SyntaxItem = symbol };
                        _aggregator.AppendIssue(issue);
                        return null;
                    }

                default:
                    CompileIssue issue2 = new CompileIssue(IssueType.Error, IssueId.SemanticBind) { SyntaxItem = symbol };
                    _aggregator.AppendIssue(issue2);
                    return null;
            }
        }
    }
}
